// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/x/events_x_utils.h"

#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <cmath>
#include <stddef.h>
#include <string.h>

#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "build/build_config.h"
#include "ui/events/devices/x11/device_data_manager_x11.h"
#include "ui/events/devices/x11/device_list_cache_x11.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/gfx/display.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"

namespace {

// Scroll amount for each wheelscroll event. 53 is also the value used for GTK+.
const int kWheelScrollAmount = 53;

const int kMinWheelButton = 4;
const int kMaxWheelButton = 7;

// A class to track current modifier state on master device. Only track ctrl,
// alt, shift and caps lock keys currently. The tracked state can then be used
// by floating device.
class XModifierStateWatcher {
public:
    static XModifierStateWatcher* GetInstance()
    {
        return base::Singleton<XModifierStateWatcher>::get();
    }

    int StateFromKeyboardCode(ui::KeyboardCode keyboard_code)
    {
        switch (keyboard_code) {
        case ui::VKEY_CONTROL:
            return ControlMask;
        case ui::VKEY_SHIFT:
            return ShiftMask;
        case ui::VKEY_MENU:
            return Mod1Mask;
        case ui::VKEY_CAPITAL:
            return LockMask;
        default:
            return 0;
        }
    }

    void UpdateStateFromXEvent(const XEvent& xev)
    {
        ui::KeyboardCode keyboard_code = ui::KeyboardCodeFromXKeyEvent(&xev);
        unsigned int mask = StateFromKeyboardCode(keyboard_code);
        // Floating device can't access the modifer state from master device.
        // We need to track the states of modifier keys in a singleton for
        // floating devices such as touch screen. Issue 106426 is one example
        // of why we need the modifier states for floating device.
        switch (xev.type) {
        case KeyPress:
            state_ = xev.xkey.state | mask;
            break;
        case KeyRelease:
            state_ = xev.xkey.state & ~mask;
            break;
        case GenericEvent: {
            XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
            switch (xievent->evtype) {
            case XI_KeyPress:
                state_ = xievent->mods.effective |= mask;
                break;
            case XI_KeyRelease:
                state_ = xievent->mods.effective &= ~mask;
                break;
            default:
                NOTREACHED();
                break;
            }
            break;
        }
        default:
            NOTREACHED();
            break;
        }
    }

    // Returns the current modifer state in master device. It only contains the
    // state of ctrl, shift, alt and caps lock keys.
    unsigned int state() { return state_; }

private:
    friend struct base::DefaultSingletonTraits<XModifierStateWatcher>;

    XModifierStateWatcher()
        : state_(0)
    {
    }

    unsigned int state_;

    DISALLOW_COPY_AND_ASSIGN(XModifierStateWatcher);
};

// Detects if a touch event is a driver-generated 'special event'.
// A 'special event' is a touch event with maximum radius and pressure at
// location (0, 0).
// This needs to be done in a cleaner way: http://crbug.com/169256
bool TouchEventIsGeneratedHack(const XEvent& xev)
{
    XIDeviceEvent* event = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    CHECK(event->evtype == XI_TouchBegin || event->evtype == XI_TouchUpdate || event->evtype == XI_TouchEnd);

    // Force is normalized to [0, 1].
    if (ui::GetTouchForceFromXEvent(xev) < 1.0f)
        return false;

    if (ui::EventLocationFromXEvent(xev) != gfx::Point())
        return false;

    // Radius is in pixels, and the valuator is the diameter in pixels.
    double radius = ui::GetTouchRadiusXFromXEvent(xev), min, max;
    unsigned int deviceid = static_cast<XIDeviceEvent*>(xev.xcookie.data)->sourceid;
    if (!ui::DeviceDataManagerX11::GetInstance()->GetDataRange(
            deviceid, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, &min, &max)) {
        return false;
    }

    return radius * 2 == max;
}

int GetEventFlagsFromXState(unsigned int state)
{
    int flags = 0;
    if (state & ShiftMask)
        flags |= ui::EF_SHIFT_DOWN;
    if (state & LockMask)
        flags |= ui::EF_CAPS_LOCK_ON;
    if (state & ControlMask)
        flags |= ui::EF_CONTROL_DOWN;
    if (state & Mod1Mask)
        flags |= ui::EF_ALT_DOWN;
    if (state & Mod2Mask)
        flags |= ui::EF_NUM_LOCK_ON;
    if (state & Mod3Mask)
        flags |= ui::EF_MOD3_DOWN;
    if (state & Mod4Mask)
        flags |= ui::EF_COMMAND_DOWN;
    if (state & Mod5Mask)
        flags |= ui::EF_ALTGR_DOWN;
    if (state & Button1Mask)
        flags |= ui::EF_LEFT_MOUSE_BUTTON;
    if (state & Button2Mask)
        flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
    if (state & Button3Mask)
        flags |= ui::EF_RIGHT_MOUSE_BUTTON;
    // There are no masks for EF_BACK_MOUSE_BUTTON and
    // EF_FORWARD_MOUSE_BUTTON.
    return flags;
}

int GetEventFlagsFromXKeyEvent(const XEvent& xev)
{
    DCHECK(xev.type == KeyPress || xev.type == KeyRelease);

#if defined(OS_CHROMEOS)
    const int ime_fabricated_flag = 0;
#else
    // XIM fabricates key events for the character compositions by XK_Multi_key.
    // For example, when a user hits XK_Multi_key, XK_apostrophe, and XK_e in
    // order to input "é", then XIM generates a key event with keycode=0 and
    // state=0 for the composition, and the sequence of X11 key events will be
    // XK_Multi_key, XK_apostrophe, **NoSymbol**, and XK_e.  If the user used
    // shift key and/or caps lock key, state can be ShiftMask, LockMask or both.
    //
    // We have to send these fabricated key events to XIM so it can correctly
    // handle the character compositions.
    const unsigned int shift_lock_mask = ShiftMask | LockMask;
    const bool fabricated_by_xim = xev.xkey.keycode == 0 && (xev.xkey.state & ~shift_lock_mask) == 0;
    const int ime_fabricated_flag = fabricated_by_xim ? ui::EF_IME_FABRICATED_KEY : 0;
#endif

    return GetEventFlagsFromXState(xev.xkey.state) | (xev.xkey.send_event ? ui::EF_FINAL : 0) | ime_fabricated_flag;
}

int GetEventFlagsFromXGenericEvent(const XEvent& xev)
{
    DCHECK(xev.type == GenericEvent);
    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    DCHECK((xievent->evtype == XI_KeyPress) || (xievent->evtype == XI_KeyRelease));
    return GetEventFlagsFromXState(xievent->mods.effective) | (xev.xkey.send_event ? ui::EF_FINAL : 0);
}

// Get the event flag for the button in XButtonEvent. During a ButtonPress
// event, |state| in XButtonEvent does not include the button that has just been
// pressed. Instead |state| contains flags for the buttons (if any) that had
// already been pressed before the current button, and |button| stores the most
// current pressed button. So, if you press down left mouse button, and while
// pressing it down, press down the right mouse button, then for the latter
// event, |state| would have Button1Mask set but not Button3Mask, and |button|
// would be 3.
int GetEventFlagsForButton(int button)
{
    switch (button) {
    case 1:
        return ui::EF_LEFT_MOUSE_BUTTON;
    case 2:
        return ui::EF_MIDDLE_MOUSE_BUTTON;
    case 3:
        return ui::EF_RIGHT_MOUSE_BUTTON;
    case 8:
        return ui::EF_BACK_MOUSE_BUTTON;
    case 9:
        return ui::EF_FORWARD_MOUSE_BUTTON;
    default:
        return 0;
    }
}

int GetButtonMaskForX2Event(XIDeviceEvent* xievent)
{
    int buttonflags = 0;
    for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) {
        if (XIMaskIsSet(xievent->buttons.mask, i)) {
            int button = (xievent->sourceid == xievent->deviceid)
                ? ui::DeviceDataManagerX11::GetInstance()->GetMappedButton(i)
                : i;
            buttonflags |= GetEventFlagsForButton(button);
        }
    }
    return buttonflags;
}

ui::EventType GetTouchEventType(const XEvent& xev)
{
    XIDeviceEvent* event = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    switch (event->evtype) {
    case XI_TouchBegin:
        return TouchEventIsGeneratedHack(xev) ? ui::ET_UNKNOWN
                                              : ui::ET_TOUCH_PRESSED;
    case XI_TouchUpdate:
        return TouchEventIsGeneratedHack(xev) ? ui::ET_UNKNOWN
                                              : ui::ET_TOUCH_MOVED;
    case XI_TouchEnd:
        return TouchEventIsGeneratedHack(xev) ? ui::ET_TOUCH_CANCELLED
                                              : ui::ET_TOUCH_RELEASED;
    }

    DCHECK(ui::TouchFactory::GetInstance()->IsTouchDevice(event->sourceid));
    switch (event->evtype) {
    case XI_ButtonPress:
        return ui::ET_TOUCH_PRESSED;
    case XI_ButtonRelease:
        return ui::ET_TOUCH_RELEASED;
    case XI_Motion:
        // Should not convert any emulated Motion event from touch device to
        // touch event.
        if (!(event->flags & XIPointerEmulated) && GetButtonMaskForX2Event(event))
            return ui::ET_TOUCH_MOVED;
        return ui::ET_UNKNOWN;
    default:
        NOTREACHED();
    }
    return ui::ET_UNKNOWN;
}

double GetTouchParamFromXEvent(const XEvent& xev,
    ui::DeviceDataManagerX11::DataType val,
    double default_value)
{
    ui::DeviceDataManagerX11::GetInstance()->GetEventData(xev, val,
        &default_value);
    return default_value;
}

void ScaleTouchRadius(const XEvent& xev, double* radius)
{
    DCHECK_EQ(GenericEvent, xev.type);
    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    ui::DeviceDataManagerX11::GetInstance()->ApplyTouchRadiusScale(xiev->sourceid,
        radius);
}

bool GetGestureTimes(const XEvent& xev, double* start_time, double* end_time)
{
    if (!ui::DeviceDataManagerX11::GetInstance()->HasGestureTimes(xev))
        return false;

    double start_time_, end_time_;
    if (!start_time)
        start_time = &start_time_;
    if (!end_time)
        end_time = &end_time_;

    ui::DeviceDataManagerX11::GetInstance()->GetGestureTimes(xev, start_time,
        end_time);
    return true;
}

int64_t g_last_seen_timestamp_ms_ = 0;
// accumulated rollover time.
int64_t g_rollover_ms_ = 0;

// Takes a 32-bit timestamp in milliseconds (e.g., an Xlib Time) and returns
// a time delta that is immune to timer rollover. This function is not thread
// safe as we do not use a lock.
base::TimeDelta TimeDeltaFromXEventTime(uint32_t timestamp)
{
    int64_t timestamp_64 = static_cast<int64_t>(timestamp);
    // Register a rollover if the distance between last timestamp and current one
    // is larger than half the width. This avoids false rollovers even in a case
    // where X server delivers reasonably close events out-of-order.
    if (g_last_seen_timestamp_ms_ - timestamp_64 > (UINT32_MAX >> 1))
        g_rollover_ms_ += static_cast<int64_t>(UINT32_MAX) + 1; // ~49.7 days.
    g_last_seen_timestamp_ms_ = timestamp_64;
    return base::TimeDelta::FromMilliseconds(g_rollover_ms_ + timestamp_64);
}

} // namespace

namespace ui {

EventType EventTypeFromXEvent(const XEvent& xev)
{
    // Allow the DeviceDataManager to block the event. If blocked return
    // ET_UNKNOWN as the type so this event will not be further processed.
    // NOTE: During some events unittests there is no device data manager.
    if (DeviceDataManager::HasInstance() && static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance())->IsEventBlocked(xev)) {
        return ET_UNKNOWN;
    }

    switch (xev.type) {
    case KeyPress:
        return ET_KEY_PRESSED;
    case KeyRelease:
        return ET_KEY_RELEASED;
    case ButtonPress:
        if (static_cast<int>(xev.xbutton.button) >= kMinWheelButton && static_cast<int>(xev.xbutton.button) <= kMaxWheelButton)
            return ET_MOUSEWHEEL;
        return ET_MOUSE_PRESSED;
    case ButtonRelease:
        // Drop wheel events; we should've already scrolled on the press.
        if (static_cast<int>(xev.xbutton.button) >= kMinWheelButton && static_cast<int>(xev.xbutton.button) <= kMaxWheelButton)
            return ET_UNKNOWN;
        return ET_MOUSE_RELEASED;
    case MotionNotify:
        if (xev.xmotion.state & (Button1Mask | Button2Mask | Button3Mask))
            return ET_MOUSE_DRAGGED;
        return ET_MOUSE_MOVED;
    case EnterNotify:
        // The standard on Windows is to send a MouseMove event when the mouse
        // first enters a window instead of sending a special mouse enter event.
        // To be consistent we follow the same style.
        return ET_MOUSE_MOVED;
    case LeaveNotify:
        return ET_MOUSE_EXITED;
    case GenericEvent: {
        TouchFactory* factory = TouchFactory::GetInstance();
        if (!factory->ShouldProcessXI2Event(const_cast<XEvent*>(&xev)))
            return ET_UNKNOWN;

        XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);

        // This check works only for master and floating slave devices. That is
        // why it is necessary to check for the XI_Touch* events in the following
        // switch statement to account for attached-slave touchscreens.
        if (factory->IsTouchDevice(xievent->sourceid))
            return GetTouchEventType(xev);

        switch (xievent->evtype) {
        case XI_TouchBegin:
            return ui::ET_TOUCH_PRESSED;
        case XI_TouchUpdate:
            return ui::ET_TOUCH_MOVED;
        case XI_TouchEnd:
            return ui::ET_TOUCH_RELEASED;
        case XI_ButtonPress: {
            int button = EventButtonFromXEvent(xev);
            if (button >= kMinWheelButton && button <= kMaxWheelButton)
                return ET_MOUSEWHEEL;
            return ET_MOUSE_PRESSED;
        }
        case XI_ButtonRelease: {
            int button = EventButtonFromXEvent(xev);
            // Drop wheel events; we should've already scrolled on the press.
            if (button >= kMinWheelButton && button <= kMaxWheelButton)
                return ET_UNKNOWN;
            return ET_MOUSE_RELEASED;
        }
        case XI_Motion: {
            bool is_cancel;
            DeviceDataManagerX11* devices = DeviceDataManagerX11::GetInstance();
            if (GetFlingDataFromXEvent(xev, NULL, NULL, NULL, NULL, &is_cancel))
                return is_cancel ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START;
            if (devices->IsScrollEvent(xev)) {
                return devices->IsTouchpadXInputEvent(xev) ? ET_SCROLL
                                                           : ET_MOUSEWHEEL;
            }
            if (devices->GetScrollClassEventDetail(xev) != SCROLL_TYPE_NO_SCROLL)
                return ET_MOUSEWHEEL;
            if (devices->IsCMTMetricsEvent(xev))
                return ET_UMA_DATA;
            if (GetButtonMaskForX2Event(xievent))
                return ET_MOUSE_DRAGGED;
            if (DeviceDataManagerX11::GetInstance()->HasEventData(
                    xievent, DeviceDataManagerX11::DT_CMT_SCROLL_X)
                || DeviceDataManagerX11::GetInstance()->HasEventData(
                    xievent, DeviceDataManagerX11::DT_CMT_SCROLL_Y)) {
                // Don't produce mouse move events for mousewheel scrolls.
                return ET_UNKNOWN;
            }

            return ET_MOUSE_MOVED;
        }
        case XI_KeyPress:
            return ET_KEY_PRESSED;
        case XI_KeyRelease:
            return ET_KEY_RELEASED;
        }
    }
    default:
        break;
    }
    return ET_UNKNOWN;
}

int EventFlagsFromXEvent(const XEvent& xev)
{
    switch (xev.type) {
    case KeyPress:
    case KeyRelease: {
        XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(xev);
        return GetEventFlagsFromXKeyEvent(xev);
    }
    case ButtonPress:
    case ButtonRelease: {
        int flags = GetEventFlagsFromXState(xev.xbutton.state);
        const EventType type = EventTypeFromXEvent(xev);
        if (type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED)
            flags |= GetEventFlagsForButton(xev.xbutton.button);
        return flags;
    }
    case EnterNotify:
    case LeaveNotify:
        return GetEventFlagsFromXState(xev.xcrossing.state);
    case MotionNotify:
        return GetEventFlagsFromXState(xev.xmotion.state);
    case GenericEvent: {
        XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);

        switch (xievent->evtype) {
        case XI_TouchBegin:
        case XI_TouchUpdate:
        case XI_TouchEnd:
            return GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective) | GetEventFlagsFromXState(XModifierStateWatcher::GetInstance()->state());
            break;
        case XI_ButtonPress:
        case XI_ButtonRelease: {
            const bool touch = TouchFactory::GetInstance()->IsTouchDevice(xievent->sourceid);
            int flags = GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective);
            if (touch) {
                flags |= GetEventFlagsFromXState(
                    XModifierStateWatcher::GetInstance()->state());
            }

            const EventType type = EventTypeFromXEvent(xev);
            int button = EventButtonFromXEvent(xev);
            if ((type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) && !touch)
                flags |= GetEventFlagsForButton(button);
            return flags;
        }
        case XI_Motion:
            return GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective);
        case XI_KeyPress:
        case XI_KeyRelease: {
            XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(xev);
            return GetEventFlagsFromXGenericEvent(xev);
        }
        }
    }
    }
    return 0;
}

base::TimeDelta EventTimeFromXEvent(const XEvent& xev)
{
    switch (xev.type) {
    case KeyPress:
    case KeyRelease:
        return TimeDeltaFromXEventTime(xev.xkey.time);
    case ButtonPress:
    case ButtonRelease:
        return TimeDeltaFromXEventTime(xev.xbutton.time);
        break;
    case MotionNotify:
        return TimeDeltaFromXEventTime(xev.xmotion.time);
        break;
    case EnterNotify:
    case LeaveNotify:
        return TimeDeltaFromXEventTime(xev.xcrossing.time);
        break;
    case GenericEvent: {
        double start, end;
        double touch_timestamp;
        if (GetGestureTimes(xev, &start, &end)) {
            // If the driver supports gesture times, use them.
            return base::TimeDelta::FromMicroseconds(end * 1000000);
        } else if (DeviceDataManagerX11::GetInstance()->GetEventData(
                       xev, DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
                       &touch_timestamp)) {
            return base::TimeDelta::FromMicroseconds(touch_timestamp * 1000000);
        } else {
            XIDeviceEvent* xide = static_cast<XIDeviceEvent*>(xev.xcookie.data);
            return TimeDeltaFromXEventTime(xide->time);
        }
        break;
    }
    }
    NOTREACHED();
    return base::TimeDelta();
}

gfx::Point EventLocationFromXEvent(const XEvent& xev)
{
    switch (xev.type) {
    case EnterNotify:
    case LeaveNotify:
        return gfx::Point(xev.xcrossing.x, xev.xcrossing.y);
    case ButtonPress:
    case ButtonRelease:
        return gfx::Point(xev.xbutton.x, xev.xbutton.y);
    case MotionNotify:
        return gfx::Point(xev.xmotion.x, xev.xmotion.y);
    case GenericEvent: {
        XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
        float x = xievent->event_x;
        float y = xievent->event_y;
#if defined(OS_CHROMEOS)
        switch (xievent->evtype) {
        case XI_TouchBegin:
        case XI_TouchUpdate:
        case XI_TouchEnd:
            ui::DeviceDataManagerX11::GetInstance()->ApplyTouchTransformer(
                xievent->deviceid, &x, &y);
            break;
        default:
            break;
        }
#endif // defined(OS_CHROMEOS)
        return gfx::Point(static_cast<int>(x), static_cast<int>(y));
    }
    }
    return gfx::Point();
}

gfx::Point EventSystemLocationFromXEvent(const XEvent& xev)
{
    switch (xev.type) {
    case EnterNotify:
    case LeaveNotify: {
        return gfx::Point(xev.xcrossing.x_root, xev.xcrossing.y_root);
    }
    case ButtonPress:
    case ButtonRelease: {
        return gfx::Point(xev.xbutton.x_root, xev.xbutton.y_root);
    }
    case MotionNotify: {
        return gfx::Point(xev.xmotion.x_root, xev.xmotion.y_root);
    }
    case GenericEvent: {
        XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
        return gfx::Point(xievent->root_x, xievent->root_y);
    }
    }

    return gfx::Point();
}

int EventButtonFromXEvent(const XEvent& xev)
{
    CHECK_EQ(GenericEvent, xev.type);
    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
    int button = xievent->detail;

    return (xievent->sourceid == xievent->deviceid)
        ? DeviceDataManagerX11::GetInstance()->GetMappedButton(button)
        : button;
}

int GetChangedMouseButtonFlagsFromXEvent(const XEvent& xev)
{
    switch (xev.type) {
    case ButtonPress:
    case ButtonRelease:
        return GetEventFlagsForButton(xev.xbutton.button);
    case GenericEvent: {
        XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev.xcookie.data);
        switch (xievent->evtype) {
        case XI_ButtonPress:
        case XI_ButtonRelease:
            return GetEventFlagsForButton(EventButtonFromXEvent(xev));
        default:
            break;
        }
    }
    default:
        break;
    }
    return 0;
}

gfx::Vector2d GetMouseWheelOffsetFromXEvent(const XEvent& xev)
{
    float x_offset, y_offset;
    if (GetScrollOffsetsFromXEvent(xev, &x_offset, &y_offset, NULL, NULL, NULL)) {
        return gfx::Vector2d(static_cast<int>(x_offset),
            static_cast<int>(y_offset));
    }

    int button = xev.type == GenericEvent ? EventButtonFromXEvent(xev)
                                          : xev.xbutton.button;

    switch (button) {
    case 4:
        return gfx::Vector2d(0, kWheelScrollAmount);
    case 5:
        return gfx::Vector2d(0, -kWheelScrollAmount);
    case 6:
        return gfx::Vector2d(kWheelScrollAmount, 0);
    case 7:
        return gfx::Vector2d(-kWheelScrollAmount, 0);
    default:
        return gfx::Vector2d();
    }
}

void ClearTouchIdIfReleasedFromXEvent(const XEvent& xev)
{
    ui::EventType type = ui::EventTypeFromXEvent(xev);
    if (type == ui::ET_TOUCH_CANCELLED || type == ui::ET_TOUCH_RELEASED) {
        ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
        ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
        double tracking_id;
        if (manager->GetEventData(xev,
                ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID,
                &tracking_id)) {
            factory->ReleaseSlotForTrackingID(tracking_id);
        }
    }
}

int GetTouchIdFromXEvent(const XEvent& xev)
{
    double slot = 0;
    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
    double tracking_id;
    if (!manager->GetEventData(
            xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
        LOG(ERROR) << "Could not get the tracking ID for the event. Using 0.";
    } else {
        ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
        slot = factory->GetSlotForTrackingID(tracking_id);
    }
    return slot;
}

float GetTouchRadiusXFromXEvent(const XEvent& xev)
{
    double radius = GetTouchParamFromXEvent(
                        xev, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, 0.0)
        / 2.0;
    ScaleTouchRadius(xev, &radius);
    return radius;
}

float GetTouchRadiusYFromXEvent(const XEvent& xev)
{
    double radius = GetTouchParamFromXEvent(
                        xev, ui::DeviceDataManagerX11::DT_TOUCH_MINOR, 0.0)
        / 2.0;
    ScaleTouchRadius(xev, &radius);
    return radius;
}

float GetTouchAngleFromXEvent(const XEvent& xev)
{
    return GetTouchParamFromXEvent(
               xev, ui::DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.0)
        / 2.0;
}

float GetTouchForceFromXEvent(const XEvent& xev)
{
    double force = 0.0;
    force = GetTouchParamFromXEvent(
        xev, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, 0.0);
    unsigned int deviceid = static_cast<XIDeviceEvent*>(xev.xcookie.data)->sourceid;
    // Force is normalized to fall into [0, 1]
    if (!ui::DeviceDataManagerX11::GetInstance()->NormalizeData(
            deviceid, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, &force))
        force = 0.0;
    return force;
}

bool GetScrollOffsetsFromXEvent(const XEvent& xev,
    float* x_offset,
    float* y_offset,
    float* x_offset_ordinal,
    float* y_offset_ordinal,
    int* finger_count)
{
    if (DeviceDataManagerX11::GetInstance()->IsScrollEvent(xev)) {
        // Temp values to prevent passing NULLs to DeviceDataManager.
        float x_offset_, y_offset_;
        float x_offset_ordinal_, y_offset_ordinal_;
        int finger_count_;
        if (!x_offset)
            x_offset = &x_offset_;
        if (!y_offset)
            y_offset = &y_offset_;
        if (!x_offset_ordinal)
            x_offset_ordinal = &x_offset_ordinal_;
        if (!y_offset_ordinal)
            y_offset_ordinal = &y_offset_ordinal_;
        if (!finger_count)
            finger_count = &finger_count_;

        DeviceDataManagerX11::GetInstance()->GetScrollOffsets(
            xev, x_offset, y_offset, x_offset_ordinal, y_offset_ordinal,
            finger_count);
        return true;
    }

    if (DeviceDataManagerX11::GetInstance()->GetScrollClassDeviceDetail(xev) != SCROLL_TYPE_NO_SCROLL) {
        double x_scroll_offset, y_scroll_offset;
        DeviceDataManagerX11::GetInstance()->GetScrollClassOffsets(
            xev, &x_scroll_offset, &y_scroll_offset);
        *x_offset = x_scroll_offset * kWheelScrollAmount;
        *y_offset = y_scroll_offset * kWheelScrollAmount;
        return true;
    }
    return false;
}

bool GetFlingDataFromXEvent(const XEvent& xev,
    float* vx,
    float* vy,
    float* vx_ordinal,
    float* vy_ordinal,
    bool* is_cancel)
{
    if (!DeviceDataManagerX11::GetInstance()->IsFlingEvent(xev))
        return false;

    float vx_, vy_;
    float vx_ordinal_, vy_ordinal_;
    bool is_cancel_;
    if (!vx)
        vx = &vx_;
    if (!vy)
        vy = &vy_;
    if (!vx_ordinal)
        vx_ordinal = &vx_ordinal_;
    if (!vy_ordinal)
        vy_ordinal = &vy_ordinal_;
    if (!is_cancel)
        is_cancel = &is_cancel_;

    DeviceDataManagerX11::GetInstance()->GetFlingData(xev, vx, vy, vx_ordinal,
        vy_ordinal, is_cancel);
    return true;
}

void ResetTimestampRolloverCountersForTesting()
{
    g_last_seen_timestamp_ms_ = 0;
    g_rollover_ms_ = 0;
}

} // namespace ui
